// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package server

import (
	"context"
	"github.com/zeebo/errs"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
	"net"
	"sync"

	"storj.io/common/identity"
	"storj.io/common/peertls/tlsopts"
	"storj.io/common/rpc"
	"storj.io/common/rpc/quic"
	"storj.io/common/rpc/rpctracing"
	"storj.io/drpc/drpcmux"
	"storj.io/drpc/drpcserver"
	jaeger "storj.io/monkit-jaeger"
)

//// Config holds server specific configuration parameters.
//type Config struct {
//	tlsopts.Config
//	Address        string `user:"true" help:"public address to listen on" default:":7777"`
//	PrivateAddress string `user:"true" help:"private address to listen on" default:"127.0.0.1:7778"`
//	P2PAdress 	   string
//	DisableQUIC    bool   `help:"disable QUIC listener on a server" hidden:"true" default:"false"`
//
//	DisableTCPTLS   bool `help:"disable TCP/TLS listener on a server" internal:"true"`
//	//DisableP2P     bool
//	//DisablePrivate     bool
//
//	DebugLogTraffic bool `hidden:"true" default:"false"` // Deprecated
//}

//type public struct {
//	tcpListener   net.Listener
//	udpConn       *net.UDPConn
//	quicListener  net.Listener
//	p2pListener  net.Listener
//	addr          net.Addr
//	disableTCPTLS bool
//	disableQUIC   bool
//	disableP2P    bool
//
//	drpc *drpcserver.Server
//	mux  *drpcmux.Mux
//}
//
//type private struct {
//	listener net.Listener
//	drpc     *drpcserver.Server
//	mux      *drpcmux.Mux
//}

// Server represents a bundle of services defined by a specific ID.
// Examples of servers are the satellite, the storagenode, and the uplink.
type SimpleServer struct {
	log        *zap.Logger
	public     public
	//private    private
	tlsOptions *tlsopts.Options

	mu   sync.Mutex
	wg   sync.WaitGroup
	once sync.Once
	done chan struct{}
}

// New creates a Server out of an Identity, a net.Listener,
// and interceptors.
func NewSimpleServer(log *zap.Logger, tlsOptions *tlsopts.Options, config Config) (_ *SimpleServer, err error) {
	server := &SimpleServer{
		log:        log,
		tlsOptions: tlsOptions,
		done:       make(chan struct{}),
	}
	server.public, err = newSimplePublic(config.Address)
	if err != nil {
		return nil, Error.Wrap(err)
	}

	return server, nil
}

//// Identity returns the server's identity.
func (p *SimpleServer) Identity() *identity.FullIdentity { return p.tlsOptions.Ident }

// Addr returns the server's public listener address.
func (p *SimpleServer) Addr() net.Addr { return p.public.addr }

// ljg
func (p *SimpleServer) UdpConn()  *net.UDPConn { return p.public.udpConn }


// DRPC returns the server's dRPC mux for registration purposes.
func (p *SimpleServer) DRPC() *drpcmux.Mux { return p.public.mux }

// PrivateDRPC returns the server's dRPC mux for registration purposes.

// Close shuts down the server.
func (p *SimpleServer) Close() error {
	p.mu.Lock()
	defer p.mu.Unlock()

	// Close done and wait for any Runs to exit.
	p.once.Do(func() { close(p.done) })
	p.wg.Wait()

	// Ensure the listeners are closed in case Run was never called.
	// We ignore these errors because there's not really anything to do
	// even if they happen, and they'll just be errors due to duplicate
	// closes anyway.
	_ = p.public.Close()
	return nil
}

// Run will run the server and all of its services.
func (p *SimpleServer) Run(ctx context.Context) (err error) {
	defer mon.Task()(&ctx)(&err)

	// Make sure the server isn't already closed. If it is, register
	// ourselves in the wait group so that Close can wait on it.
	p.mu.Lock()
	select {
	case <-p.done:
		p.mu.Unlock()
		return errs.New("server closed")
	default:
		p.wg.Add(1)
		defer p.wg.Done()
	}
	p.mu.Unlock()


	if p.public.udpConn != nil {
		p.public.quicListener, err = quic.NewListener(p.public.udpConn, p.tlsOptions.ServerTLSConfig(), nil)
		if err != nil {
			return err
		}
	}


	var muxGroup errgroup.Group

	// Now we launch all the stuff that uses the listeners.
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	var group errgroup.Group
	group.Go(func() error {
		select {
		case <-p.done:
			cancel()
		case <-ctx.Done():
		}

		return nil
	})



	if p.public.quicListener != nil {
		group.Go(func() error {
			defer cancel()
			return p.public.drpc.Serve(ctx, wrapListener(p.public.quicListener))
		})
	}

	// Now we wait for all the stuff using the listeners to exit.
	err = group.Wait()

	// Now we close down our listeners.
	return errs.Combine(err, muxGroup.Wait())
}

func newSimplePublic(publicAddr string) (public, error) {
	var (
		publicUDPConn     *net.UDPConn
	)

	for retry := 0; ; retry++ {
		addr := publicAddr
		{
			udpAddr, err := net.ResolveUDPAddr("udp", addr)
			if err != nil {
				return public{}, err
			}

			publicUDPConn, err = net.ListenUDP("udp", udpAddr)
			if err != nil {
				_, port, _ := net.SplitHostPort(publicAddr)
				if port == "0" && retry < 10 && isErrorAddressAlreadyInUse(err) {
					// from here, we know for sure that the tcp port chosen by the
					// os is available, but we don't know if the same port number
					// for udp is also available.
					// if a udp port is already in use, we will close the tcp port and retry
					// to find one that is available for both udp and tcp.
					continue
				}
				return public{}, err
			}
		}
		break
	}

	publicMux := drpcmux.New()
	publicTracingHandler := rpctracing.NewHandler(publicMux, jaeger.RemoteTraceHandler)
	serverOptions := drpcserver.Options{
		Manager: rpc.NewDefaultManagerOptions(),
	}

	var netAddr net.Addr


	if publicUDPConn != nil  {
		netAddr = publicUDPConn.LocalAddr()
	}

	return public{
		tcpListener:   nil,
		udpConn:       publicUDPConn,
		p2pListener:   nil,
		addr:          netAddr,
		drpc:          drpcserver.NewWithOptions(publicTracingHandler, serverOptions),
		mux:           publicMux,
		disableTCPTLS: true,
		disableQUIC:   false,
		disableP2P:    true,
	}, nil
}

